package XMLParser;

import java.awt.Color;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Pattern;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Unmarshaller;

import Cards.Card;
import Cards.Money_card;
import Cards.Move_card;
import Cards.Card.CardType;
import Cards.Move_card.eGoTo;
import Monopol.AssetGroups.AllServices;
import Monopol.AssetGroups.AllTransportations;
import Monopol.AssetGroups.State;
import Square.City;
import Square.Service;
import Square.Square;
import Square.Transportation;
import Square.Square.fixedSquares;
import Utils.Utils;
import XMLParser.monopoly.jaxb.CardBase;
import XMLParser.monopoly.jaxb.CityType;
import XMLParser.monopoly.jaxb.GetOutOfJailCard;
import XMLParser.monopoly.jaxb.GotoCard;
import XMLParser.monopoly.jaxb.GotoJailSquareType;
import XMLParser.monopoly.jaxb.JailSlashFreeSpaceSquareType;
import XMLParser.monopoly.jaxb.MonetaryCard;
import XMLParser.monopoly.jaxb.Monopoly;
import XMLParser.monopoly.jaxb.ParkingSquareType;
import XMLParser.monopoly.jaxb.SimpleAssetType;
import XMLParser.monopoly.jaxb.SquareBase;
import XMLParser.monopoly.jaxb.SquareType;
import XMLParser.monopoly.jaxb.StartSquareType;
import XMLParser.monopoly.jaxb.Monopoly.Board;
import XMLParser.monopoly.jaxb.Monopoly.Surprise;
import XMLParser.monopoly.jaxb.Monopoly.Warrant;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.net.URL;

/**
 * 
 */
/**
 * @author Avia Tam
 * @author Assaf Miron
 *
 */
public class MonopolyXML {

    public enum JAXBCardTypes {

	MONETARY_CARD, GOTO_CARD, GETOUT_CARD
    }
    private State[] allStates;
    private AllTransportations allTransportaions;
    private AllServices allServices;
    private ArrayList<Card> surpriseDeck;
    private ArrayList<Card> warningDeck;
    private Square[] squaresOrder;

    ;
    private Boolean parseOK = true;

    /**
     *
     */
    public MonopolyXML(URL configFile) {
	super();
	JAXBContext jaxbContext;
	try {
	    // Create a New Instance of the Monopoly JAXB Package
	    jaxbContext = JAXBContext.newInstance(Monopoly.class);
	    parseXML(jaxbContext, configFile.openStream());
	} catch (JAXBException e) {
	    parseOK = false;
	    // Print out the Error Stack Trace
	    e.printStackTrace();
	} catch (IOException ex) {
	    parseOK = false;
	    // Print out the Error Stack Trace
	    ex.printStackTrace();
	}

    }

    public MonopolyXML(String xml) {
	super();
	JAXBContext jaxbContext;
	try {
	    // Create a New Instance of the Monopoly JAXB Package
	    jaxbContext = JAXBContext.newInstance("XMLParser.monopoly.jaxb");
	    InputStream is;
	    try {
		is = new ByteArrayInputStream(xml.getBytes("UTF-8"));
		parseXML(jaxbContext, is);
	    } catch (UnsupportedEncodingException ex) {
		Logger.getLogger(MonopolyXML.class.getName()).log(Level.SEVERE, null, ex);
	    }

	} catch (JAXBException e) {
	    parseOK = false;
	    // Print out the Error Stack Trace
	    e.printStackTrace();
	}
    }

    private void parseXML(JAXBContext jaxbContext, InputStream configFile) {
	try {
	    // Create an Unmarsheller for the JAXB Context
	    Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
	    // Unmarshall the XML Object from the Configuration File
	    Monopoly jaxbMonopoly = (Monopoly) unmarshaller.unmarshal(configFile);
	    getAssets(jaxbMonopoly.getAssets());
	    surpriseDeck = getSurpriseCards(jaxbMonopoly.getSurprise());
	    warningDeck = getWarningCards(jaxbMonopoly.getWarrant());
	    squaresOrder = setBoardOrder(jaxbMonopoly.getBoard());
	} catch (Exception ex) {
	    parseOK = false;
	    // Print out the Error Stack Trace
	    ex.printStackTrace();
	}
    }

    /**
     * @return the states
     */
    public State[] getStates() {
	return allStates;
    }

    /**
     * @return the allTransportaions
     */
    public AllTransportations getAllTransportaions() {
	return allTransportaions;
    }

    /**
     * @return the allServices
     */
    public AllServices getAllServices() {
	return allServices;
    }

    /**
     * @return the surpriseDeck
     */
    public ArrayList<Card> getSurpriseDeck() {
	return surpriseDeck;
    }

    /**
     * @return the warningDeck
     */
    public ArrayList<Card> getWarningDeck() {
	return warningDeck;
    }

    /**
     * @return the squaresOrder
     */
    public Square[] getSquaresOrder() {
	return squaresOrder;
    }

    /**
     * Create all the Monopoly Game Assets Groups from a Configuration JAXB Object
     * @param assetsConfig The Assets JAXB Configuration Object
     */
    public void getAssets(Monopoly.Assets assetsConfig) {
	allStates = getStates(assetsConfig.getCountries());
	allTransportaions = getTransportations(assetsConfig.getTransportations());
	allServices = getServices(assetsConfig.getUtilities());
    }

    /**
     * Creates the States For the Monopoly Game from a Configuration JAXB Object
     * @param countriesConfig The Countries JAXB Configuration Object
     * @return An Array of {@link State}
     */
    public State[] getStates(Monopoly.Assets.Countries countriesConfig) {
	// Create a Color Array for the States Colors
	Color[] stateColors = {Color.pink, Color.blue, Color.yellow, Color.black, Color.green, Color.red, Color.magenta, Color.cyan, Color.orange, Color.getColor("Purple"), Color.darkGray};
	// Get the Countries from the Configuration
	List<Monopoly.Assets.Countries.Country> countries = countriesConfig.getCountry();
	// Create a State Array the Size of the Countries from the Configuration File
	State[] states = new State[countries.size()];
	// Create an Index Counter
	Integer i = 0;
	// Create a State Object From the Countries in the File
	for (Monopoly.Assets.Countries.Country oCountry : countries) {
	    // Create the State
	    states[i] = new State(oCountry.getName(), stateColors[i]);
	    // Create the Cities to the State
	    for (CityType oCity : oCountry.getCity()) {
		// Add the Cities to the State
		states[i].addCity(getCity(oCity, states[i]));
	    }
	    // Increment the Index Counter
	    i++;
	}
	// Return the States
	return states;
    }

    /**
     * Creates the Transportations For the Monopoly Game from a Configuration JAXB Object
     * @param transpConfig The Transportation JAXB Configuration Object
     * @return An All Transportation Monopoly Object as {@link AllTransportations}
     */
    public AllTransportations getTransportations(Monopoly.Assets.Transportations transpConfig) {
	AllTransportations retAllTransportations = new AllTransportations();
	// Get the Transportations from the Configuration
	List<SimpleAssetType> transportations = transpConfig.getTransportation();
	// Get the Cost if Player has All the Transportations
	Double dAllTransCost = ((Long) transpConfig.getStayCost()).doubleValue();
	// Create the Transportation Objects
	for (SimpleAssetType oTransportation : transportations) {
	    // Create and Add a New Transportation
	    retAllTransportations.createTransportation(
		    oTransportation.getName(), // Set the Transportation Name
		    // Set the Transportation Stay Cost as a Double Array
		    Utils.createDoubleArray(
		    // If Only part of the Transportation belong to a Player
		    ((Long) oTransportation.getStayCost()).doubleValue(),
		    // If All of the Transportations Belong to a Single Player
		    dAllTransCost),
		    // Set the Transportation Cost
		    ((Long) oTransportation.getCost()).doubleValue());
	}
	// Return All of the Transportations
	return retAllTransportations;
    }

    /**
     * Creates the Services For the Monopoly Game from a Configuration JAXB Object
     * @param utilConfig The Utilities JAXB Configuration object
     * @return An All Services Monopoly Object as {@link AllServices}
     */
    public AllServices getServices(Monopoly.Assets.Utilities utilConfig) {
	AllServices retAllServices = new AllServices();
	// Get the Utilities from the Configuration
	List<SimpleAssetType> utilities = utilConfig.getUtility();
	// Get the Cost if the Player has All the Utilities
	Double dAllUtils = ((Long) utilConfig.getStayCost()).doubleValue();
	// Create the Services Objects
	for (SimpleAssetType oUtility : utilities) {
	    // Create and Add a New Service
	    retAllServices.createService(
		    oUtility.getName(), // Set the Service Name
		    // Set the Services Stay Cost as a Double Array
		    Utils.createDoubleArray(
		    // If only Part of the Services Belong to a Player
		    ((Long) oUtility.getStayCost()).doubleValue(),
		    // If All of the Services belong to a Single Player
		    dAllUtils),
		    // Set the Service Cost
		    ((Long) oUtility.getCost()).doubleValue());
	}
	// Return All of the Services
	return retAllServices;
    }

    /**
     * Create a City of an JAXB City Type
     * @param oCity The JAXB City Type
     * @param oState The State Object the City belongs to
     * @return A Monopoly {@link City}
     */
    private City getCity(CityType oCity, State oState) {
	// Return the New City Created
	return new City(oCity.getName(), // Set the Name of the City
		// Set the Stay Cost as a Double Array
		Utils.createDoubleArray(((Long) oCity.getStayCost()).doubleValue(), // With No Houses
		((Long) oCity.getStayCost1()).doubleValue(), // With 1 House
		((Long) oCity.getStayCost2()).doubleValue(), // With 2 Houses
		((Long) oCity.getStayCost3()).doubleValue()), // With 3 Houses
		// Set the City Buy Cost
		((Long) oCity.getCost()).doubleValue(),
		// Set the House Cost for the City
		((Long) oCity.getHouseCost()).doubleValue(),
		// Set the City State
		oState);
    }

    /**
     * Create the Surprise Card Deck
     * @param surprise The Surprise JAXB Configuration Deck
     * @return A Monopoly Surprise Cards Deck
     */
    public ArrayList<Card> getSurpriseCards(Surprise surprise) {
	ArrayList<Card> retSurprises = new ArrayList<Card>();
	List<CardBase> surpriseCards = surprise.getSurpriseCards();
	for (CardBase oCard : surpriseCards) {
	    // Check witch Card we are handling
	    switch (getCardType(oCard)) {
		case MONETARY_CARD: {
		    // Convert The Card Object to a Monetary Card
		    MonetaryCard monetaryCard = (MonetaryCard) oCard;
		    // Check from whom to pay
		    Boolean payToCashire = (monetaryCard.getWho().equalsIgnoreCase("TREASURY"));
		    // Duplicate the Number of Cards Acording to the Number in 'num'
		    for (int i = 0; i < oCard.getNum(); ++i) {
			// Add the New Money Card to the Surprise Cards Array
			retSurprises.add(new Money_card(CardType.SURPRISE_CARD, // Set as a Surprise Card
				String.format(monetaryCard.getText(), monetaryCard.getSum()), // Set the Card Parsed Text
				((Long) monetaryCard.getSum()).doubleValue(), // Set the Amount to Pay
				payToCashire)); // Set form whom to get Payed (Cashier / Players)
		    }
		    break;
		}
		case GOTO_CARD: {
		    // Convert the Card Object to a Go To Card
		    GotoCard gotoCard = (GotoCard) oCard;
		    // Duplicate the Number of Cards Acording to the Number in 'num'
		    for (int i = 0; i < oCard.getNum(); ++i) {
			// Add the New Move Card to the Surprise Cards Array
			retSurprises.add(new Move_card(CardType.SURPRISE_CARD, // Set as a Surprise Card
				gotoCard.getText(), // Set the Card Description Text
				parseGoTo(gotoCard.getTo()))); // Set to Where to Go
		    }
		    break;
		}
		case GETOUT_CARD: {
		    // Convert the card Object to a Get Out of Jail Card
		    GetOutOfJailCard jailCard = (GetOutOfJailCard) oCard;
		    retSurprises.add(new Card(CardType.SURPRISE_CARD, // Set as a Surprise Card
			    jailCard.getText())); // Set the Card Description Text
		    break;
		}
	    }
	}
	// Return all the Surprise Cards
	return retSurprises;
    }

    /**
     * Create the Warning Card Deck
     * @param warrant The Warrant JAXB Configuration Deck
     * @return A Monopoly Warning Cards Deck
     */
    public ArrayList<Card> getWarningCards(Warrant warrant) {
	ArrayList<Card> retWarnings = new ArrayList<Card>();
	List<CardBase> warrantCards = warrant.getWarrantCards();
	for (CardBase oCard : warrantCards) {
	    // Check witch Card we are handling
	    switch (getCardType(oCard)) {
		case MONETARY_CARD: {
		    // Convert The Card Object to a Monetary Card
		    MonetaryCard monetaryCard = (MonetaryCard) oCard;
		    // Check from whom to pay
		    Boolean payToCashire = (monetaryCard.getWho().equalsIgnoreCase("TREASURY"));
		    // Duplicate the Number of Cards Acording to the Number in 'num'
		    for (int i = 0; i < oCard.getNum(); ++i) {
			// Add the New Money Card to the Warning Cards Array
			retWarnings.add(new Money_card(CardType.WARNING_CARD, // Set as a Warning Card
				String.format(monetaryCard.getText(), monetaryCard.getSum()), // Set the Card Parsed Text
				((Long) (monetaryCard.getSum())).doubleValue(), // Set the Amount to Pay
				payToCashire)); // Set to whom to Pay (Cashier / Players)
		    }
		    break;
		}
		case GOTO_CARD: {
		    // Convert the Card Object to a Go To Card
		    GotoCard gotoCard = (GotoCard) oCard;
		    // Duplicate the Number of Cards Acording to the Number in 'num'
		    for (int i = 0; i < oCard.getNum(); ++i) {
			// Add the New Move Card to the Warning Cards Array
			retWarnings.add(new Move_card(CardType.WARNING_CARD, // Set as a Warning Card
				gotoCard.getText(), // Set the Card Description Text
				parseGoTo(gotoCard.getTo()))); // Set to Where to Go
		    }
		    break;
		}
	    }
	}
	// Return all the Warning Cards
	return retWarnings;
    }

    /**
     * Checks the Card Type
     * @param oCard The Card Object
     * @return The Card JAXB Monopoly Configuration Type
     */
    private JAXBCardTypes getCardType(CardBase oCard) {
	JAXBCardTypes ret = null;
	// Check if the Card is a Monetary Card
	if (oCard instanceof MonetaryCard) {
	    ret = JAXBCardTypes.MONETARY_CARD;
	} // Check if the Card is a Goto Card
	else if (oCard instanceof GotoCard) {
	    ret = JAXBCardTypes.GOTO_CARD;
	} // Check if the Card is a Get out of Prison Card
	else if (oCard instanceof GetOutOfJailCard) {
	    ret = JAXBCardTypes.GETOUT_CARD;
	}
	return ret;
    }

    /**
     * Converts any known Place to go to in the Game to a Known Monopoly Enumerator Value
     * @param sGoto The Place to go to
     * @return A {@link eGoTo} Enumerator of the Places to Go To
     */
    private eGoTo parseGoTo(String sGoto) {
	// Check if the String contains any of the Known Places to Go to
	eGoTo ret = null;
	// Check if has the Word Start
	if (Pattern.compile(Pattern.quote("START"), Pattern.CASE_INSENSITIVE).matcher(sGoto).find()) {
	    ret = eGoTo.START;
	} // Check if has the Word Surprise
	else if (Pattern.compile(Pattern.quote("SURPRISE"), Pattern.CASE_INSENSITIVE).matcher(sGoto).find()) {
	    ret = eGoTo.NEXT_SURPRISE;
	} // Check if has the Word Warrant OR Warning
	else if (Pattern.compile(Pattern.quote("WARRANT"), Pattern.CASE_INSENSITIVE).matcher(sGoto).find()) {
	    ret = eGoTo.NEXT_WARNING;
	} else if (Pattern.compile(Pattern.quote("WARNING"), Pattern.CASE_INSENSITIVE).matcher(sGoto).find()) {
	    ret = eGoTo.NEXT_WARNING;
	} // Check if has the Word Jail OR Prison
	else if (Pattern.compile(Pattern.quote("JAIL"), Pattern.CASE_INSENSITIVE).matcher(sGoto).find()) {
	    ret = eGoTo.PRISON;
	} else if (Pattern.compile(Pattern.quote("PRISON"), Pattern.CASE_INSENSITIVE).matcher(sGoto).find()) {
	    ret = eGoTo.PRISON;
	}

	// Return the Requested Goto Enumerator
	return ret;
    }

    /**
     * Sets the Game Board Order From the JAXB Configuration Object
     * @param board The JAXB Configuration Object
     * @return An Array of the Squares by the Order from the Configuration File
     */
    public Square[] setBoardOrder(Board board) throws Exception {
	// Create the Squares Array to Return
	Square[] retBoadSquares = new Square[36];
	// Get All the Cities in the Game
	City[] cities = getCitiesArray();
	// Get all the Transportations in the Game
	Transportation[] arrTransp = getTransportationsArray();
	// Get all the Services in the Game
	Service[] arrServ = getServicesArray();
	// Create an Index Counter for the Board
	Integer i = 0;
	// Create an Index Counter for the Assets (Cities, Transportations, Services)
	Integer c = 0, t = 0, s = 0;
	// Get the JAXB Board Content
	List<JAXBElement<? extends SquareBase>> jaxbBoard = board.getContent();
	try {
	    if (jaxbBoard.size() > 30) {
		// Loop over all the input Squares
		for (JAXBElement<? extends SquareBase> oSquare : jaxbBoard) {

		    // Check if this is the Start Square
		    if (oSquare.getValue() instanceof StartSquareType) {
			fixedSquares.START_SQUARE.set(i);
			retBoadSquares[i] = new Square("Start");
		    } // Check if this is the Parking Square
		    else if (oSquare.getValue() instanceof ParkingSquareType) {
			fixedSquares.PARKING_SQUARE.set(i);
			retBoadSquares[i] = new Square("Parking");
		    } // Check if this is the Prison Square
		    else if (oSquare.getValue() instanceof JailSlashFreeSpaceSquareType) {
			fixedSquares.PRISON_SQUARE.set(i);
			retBoadSquares[i] = new Square("Prison");
		    } // Check if this is the Go to Prison Square
		    else if (oSquare.getValue() instanceof GotoJailSquareType) {
			fixedSquares.GO2PRISON_SQUARE.set(i);
			retBoadSquares[i] = new Square("Go to Prison");
		    } // This must be a normal Square
		    else {
			// Convert the Square Object to a Square Type
			SquareType squareType = (SquareType) oSquare.getValue();
			// Check if its a City
			if (squareType.getType().equals("CITY")) {
			    retBoadSquares[i] = cities[c];
			    // Increment the Cities Index Counter
			    c++;
			} // Check if its a Transportation
			else if (squareType.getType().equals("TRANSPORTATION")) {
			    retBoadSquares[i] = arrTransp[t];
			    // Increment the Transportations Counter
			    t++;
			} // Check if its a Utility
			else if (squareType.getType().equals("UTILITY")) {
			    retBoadSquares[i] = arrServ[s];
			    // Increment the Services Index Counter
			    s++;
			} // Check if its a Surprise Square
			else if (squareType.getType().equals("SURPRISE")) {
			    retBoadSquares[i] = new Square("Surprise");
			} // Check if its a Warrant Square
			else if (squareType.getType().equals("WARRANT")) {
			    retBoadSquares[i] = new Square("Warning");
			} // Else - Unknown Type
			else {
			    System.out.println(String.format("Error!\nSquare Type Unknown (Type: %s)", squareType.getType()));
			}
		    }
		    // Increment the Board Index Counter
		    i++;
		} // End of For Loop
	    }
	} catch (Exception e) {
	    // This should not Get Here
	    e.printStackTrace();
	}
	// A Normal Monopoly Board has 36 Squares
	// Check that the Number of Squares from the XML file is 36
	if (i < 35) {
	    // Inform the User
//	    System.out.println("Number of Squares in the File is to small.\nOnly " + i + " squares in the File.");
	    parseOK = false;
	    // Exit with Error
//            System.exit(1);
	}
	// Return the New Board Order
	return retBoadSquares;
    }

    /**
     * @return An Array of All the Services
     */
    private Service[] getServicesArray() {
	Service[] arrServices = new Service[0];
	return allServices.getAssets().toArray(arrServices);
    }

    /**
     * @return An Array of All the Transportations
     */
    private Transportation[] getTransportationsArray() {
	Transportation[] arrTransport = new Transportation[0];
	return allTransportaions.getAssets().toArray(arrTransport);
    }

    /**
     * Creates a Unified Array for all the Cities in the Game
     * @return  The Cities Array
     */
    private City[] getCitiesArray() {
	ArrayList<City> retCities = new ArrayList<City>();
	City[] arrCities = new City[0];
	try {
	    // Loop over All the States
	    for (State oState : allStates) {
		for (City oCity : oState.getCities()) {

		    retCities.add(oCity);
		}
	    }
	} catch (Exception e) {
	    // This should not Get Here
	    e.printStackTrace();
	}
	// Return the Cities Array
	return retCities.toArray(arrCities);
    }

    /**
     * @return The Parsing Status
     */
    public boolean getParseOK() {
	return parseOK;
    }
}
